<template>
  <el-table-column
    v-if="isVisible && (type === 'selection' && scrollLoad)"
    v-bind="bindProps"
  >
    <template v-slot:header="scope">
      <el-checkbox
        v-model="checkAll"
        :indeterminate="isIndeterminate"
        @change="checkAllEvent"
      ></el-checkbox>
    </template>
    <template v-slot="scope">
      <el-checkbox
        v-model="scope.row.scrollChecked"
        @change="checkRowEvent(scope)"
      ></el-checkbox>
    </template>
  </el-table-column>
  <el-table-column
    v-else-if="isVisible && (type === 'selection' || group || isGroup)"
    v-bind="bindProps"
  >
    <slot></slot>
  </el-table-column>
  <el-table-column
    v-else-if="isVisible && type === 'index'"
    v-bind="bindProps"
  >
    <template v-slot:header="scope">
      <slot
        name="header"
        v-bind="getHeadScope(scope)"
      >{{ label || '#' }}</slot>
    </template>
    <template v-slot="scope">
      <slot v-bind="getIndexScope(scope)">{{ formatRowIndex(scope) }}</slot>
    </template>
  </el-table-column>
  <el-table-column
    v-else-if="isVisible && type === 'isrender'"
    v-bind="bindProps"
  >
    <template v-if="type === 'isrender'" v-slot="scope">
      <Cell
        :column="scope.column"
        :row="scope.row.data"
        :index="scope.$index"
        :render="render"
      />
    </template>
  </el-table-column>
  <el-table-column
    v-else-if="isVisible && type === 'expand'"
    v-bind="bindProps"
  >
    <template v-slot:header="scope">
      <slot
        name="header"
        v-bind="getHeadScope(scope)"
      ></slot>
    </template>
    <template v-slot="scope">
      <Cell
        :column="scope.column"
        :row="scope.row.data"
        :index="scope.$index"
        :render="render"
      />
    </template>
  </el-table-column>
  <el-table-column
    v-else-if="isVisible && editRender"
    v-bind="bindProps"
  >
    <template v-slot:header="scope">
      <slot
        name="header"
        v-bind="getHeadScope(scope)"
      >
        <i
          v-if="checkRequired(scope)"
          class="editable-required-icon"
        ></i>
        <i
          v-if="checkIcon(scope)"
          class="el-icon-edit-outline editable-header-icon"
        ></i>
        {{ scope.column.label }}
      </slot>
    </template>
    <template v-slot="scope">
      <template v-if="isEditRender(scope)">
        <slot
          name="edit"
          v-bind="getRowScope(scope)"
        >
          <template v-if="compName === 'ElSelect'">
            <el-select
              v-if="renderOpts.optionGroups"
              v-model="scope.row.data[scope.column.property]"
              v-bind="getRendProps(scope)"
              v-on="getRendEvents(scope)"
            >
              <el-option-group
                v-for="(group, gIndex) in renderOpts.optionGroups"
                :key="gIndex"
                :label="group[renderOpts.optionGroupProps.label]"
                v-bind="group.props"
              >
                <el-option
                  v-for="(item, index) in group[renderOpts.optionGroupProps.options]"
                  :key="index"
                  :value="item[renderOpts.optionProps.value]"
                  :label="item[renderOpts.optionProps.label]"
                  v-bind="item.props || item.attrs"
                ></el-option>
              </el-option-group>
            </el-select>
            <el-select
              v-else
              v-model="scope.row.data[scope.column.property]"
              v-bind="getRendProps(scope)"
              v-on="getRendEvents(scope)"
            >
              <el-option
                v-for="(item, index) in renderOpts.options"
                :key="index"
                :value="item[renderOpts.optionProps.value]"
                :label="item[renderOpts.optionProps.label]"
                v-bind="item.props || item.attrs"
              ></el-option>
            </el-select>
          </template>
          <template v-else-if="comps.includes(compName)">
            <component
              :is="compName"
              v-model="scope.row.data[scope.column.property]"
              v-bind="getRendProps(scope)"
              v-on="getRendEvents(scope)"
            ></component>
          </template>
          <template v-else>
            <el-input
              v-model="scope.row.data[scope.column.property]"
              v-bind="getRendProps(scope)"
              v-on="getRendEvents(scope)"
            ></el-input>
          </template>
        </slot>
      </template>
      <template v-else>
        <slot v-bind="getRowScope(scope)">{{ formatColumnLabel(scope) }}</slot>
      </template>
      <template v-if="scope.row.validActive && !editConfig.disabledValidTip && scope.row.validActive === scope.column.property">
        <template v-if="editConfig.useDefaultValidTip">
          <template v-if="scope.row.showValidMsg">
            <slot
              name="valid"
              v-bind="getVaildScope(scope)"
            >
              <div class="editable-valid_error">
                <span class="valid-message">{{ scope.row.validRule ? scope.row.validRule.message : '' }}</span>
              </div>
            </slot>
          </template>
        </template>
        <template v-else>
          <el-tooltip
            :value="scope.row.showValidMsg"
            v-bind="editConfig.validTooltip"
            popper-class="tooltip"
          >
            <div class="editable-valid_wrapper"></div>
            <template v-slot:content>
              <slot
                name="valid"
                v-bind="getVaildScope(scope)"
              >
                <div class="valid-message">{{ scope.row.validRule ? scope.row.validRule.message : '' }}</div>
              </slot>
            </template>
          </el-tooltip>
        </template>
      </template>
    </template>
  </el-table-column>
  <el-table-column
    v-else-if="isVisible"
    v-bind="bindProps"
  >
    <template v-slot:header="scope">
      <slot
        name="header"
        v-bind="getHeadScope(scope)"
      >{{ scope.column.label }}</slot>
    </template>
    <template v-slot="scope">
      <slot v-bind="getRowScope(scope)">{{ formatColumnLabel(scope) }}</slot>
    </template>
  </el-table-column>
</template>

<script>
import XEUtils from "xe-utils";
import PropsStatic from "./props";
import Cell from "./expand";

export default {
  name: "ElxEditableColumn",
  props: {
    group: Boolean,
    editRender: Object,
    ...PropsStatic.tableColumn,
    render: Function,
  },
  components: {
    Cell
  },
  inject: ["$editable"],
  data() {
    return {
      checkAll: false,
      isIndeterminate: false,
      // 支持内置组件
      comps: [
        "ElAutocomplete",
        "ElInput",
        "ElSelect",
        "ElCascader",
        "ElTimeSelect",
        "ElTimePicker",
        "ElDatePicker",
        "ElInputNumber",
        "ElSwitch",
        "ElRate",
        "ElColorPicker",
        "ElSlider"
      ]
    };
  },
  computed: {
    renderOpts() {
      let editRender = this.editRender;
      return Object.assign(
        {
          name: editRender ? "ElInput" : null,
          type: "default",
          autofocus:
            editRender &&
            ["ElInput", "ElInputNumber", "ElAutocomplete"].includes(
              editRender.name
            ),
          optionProps: {
            value: "value",
            label: "label"
          },
          optionGroupProps: {
            options: "options",
            label: "label"
          }
        },
        editRender
      );
    },
    editConfig() {
      return this.$editable.configs;
    },
    scrollLoad() {
      return this.$editable.scrollLoad;
    },
    isReadonly() {
      return !this.editRender;
    },
    compName() {
      return this.renderOpts.name;
    },
    isGroup() {
      return (
        this.$slots.default &&
        this.$slots.default.some(
          item =>
            item &&
            item.componentOptions &&
            item.componentOptions.tag === "elx-editable-column"
        )
      );
    },
    customColumnList() {
      return this.$editable.columnList;
    },
    isVisible() {
      if (this.prop && this.customColumnList && this.customColumnList.length) {
        let column = this.customColumnList.find(
          item => item.prop === this.prop
        );
        return column ? column.visible : true;
      }
      return true;
    },
    bindProps() {
      let sortBy;
      let clsName = this.isReadonly ? "elx_readonly " : "elx_edit ";
      if (this.className) {
        clsName += `${this.className} `;
      }
      if (this.renderOpts.autofocus) {
        clsName += "elx_autofocus ";
      }
      if (XEUtils.isFunction(this.sortBy)) {
        sortBy = this.sortBy;
      } else if (XEUtils.isString(this.sortBy)) {
        sortBy = `data.${this.sortBy}`;
      } else if (XEUtils.isArray(this.sortBy)) {
        sortBy = this.sortBy.map(name => `data.${name}`);
      } else if (this.sortable && this.prop) {
        sortBy = `data.${this.prop}`;
      }
      return {
        type: this.scrollLoad ? null : this.type,
        label: this.label,
        columnKey: this.columnKey,
        prop: this.prop,
        width: this.width,
        minWidth: this.minWidth,
        fixed: this.fixed,
        sortable: this.sortable,
        sortMethod: this.sortMethod ? this.sortMethodEvent : this.sortMethod,
        sortBy,
        sortOrders: this.sortOrders,
        resizable: this.resizable,
        showOverflowTooltip: this.showOverflowTooltip,
        align: this.align,
        headerAlign: this.headerAlign,
        className: `elx-editable-column ${XEUtils.trimRight(clsName)}`,
        labelClassName: this.labelClassName,
        selectable: this.selectableEvent,
        reserveSelection: this.reserveSelection,
        filters: this.filters,
        filterPlacement: this.filterPlacement,
        filterMultiple: this.filterMultiple,
        filterMethod: this.filterMethod
          ? this.filterMethodEvent
          : this.filterMethod,
        filteredValue: this.filteredValue,
        expandRender: this.expandRender,
      };
    }
  },
  methods: {
    getHeadScope(scope) {
      return {
        column: scope.column,
        store: scope.store,
        $index: scope.$index,
        $render: this.renderOpts,
        _self: scope._self
      };
    },
    getIndexScope(scope) {
      return {
        row: scope.row.data,
        column: scope.column,
        store: scope.store,
        $index: this.getRowIndex(scope),
        $render: this.renderOpts,
        _self: scope._self,
        _row: scope.row
      };
    },
    getRowScope(scope) {
      return {
        row: scope.row.data,
        column: scope.column,
        store: scope.store,
        $index: scope.$index,
        $render: this.renderOpts,
        _self: scope._self,
        _row: scope.row
      };
    },
    getVaildScope(scope) {
      return {
        rule: scope.row.validRule || {},
        row: scope.row.data,
        column: scope.column,
        store: scope.store,
        $index: scope.$index,
        $render: this.renderOpts,
        _self: scope._self,
        _row: scope.row
      };
    },
    getRowIdentity(row, column) {
      return XEUtils.get(row.data, column.property);
    },
    getRendProps(scope) {
      let size = this.$editable.size;
      let props = this.renderOpts.props || this.renderOpts.attrs;
      return size ? Object.assign({ size }, props) : props;
    },
    getRendEvents({ $index, row, column, store }) {
      let type = "change";
      let scope = {
        $index,
        row: row.data,
        column,
        store,
        $render: this.renderOpts,
        _row: row
      };
      switch (this.compName) {
        case "ElAutocomplete":
          type = "select";
          break;
        case "ElInput":
        case "ElInputNumber":
          type = "input";
          break;
      }
      let defEvent = { [type]: evnt => this.$editable.updateStatus(scope) };
      if (this.renderOpts.events) {
        return Object.assign(
          defEvent,
          XEUtils.objectMap(
            this.renderOpts.events,
            cb =>
              function() {
                cb.apply(null, [scope].concat(Array.from(arguments)));
              }
          )
        );
      }
      return defEvent;
    },
    getRowIndex(scope) {
      return this.$editable.visibleStart + scope.$index;
    },
    formatRowIndex(scope) {
      let $index = this.getRowIndex(scope);
      return this.index ? this.index($index) : $index + 1;
    },
    formatColumnLabel(scope) {
      if (this.formatter) {
        return this.formatter(
          scope.row.data,
          scope.column,
          this.getRowIdentity(scope.row, scope.column),
          scope.$index
        );
      }
      switch (this.compName) {
        case "ElSelect":
          return this.getSelectLabel(scope);
        case "ElCascader":
          return this.getCascaderLabel(scope);
        case "ElTimePicker":
          return this.getTimePickerLabel(scope);
        case "ElDatePicker":
          return this.getDatePickerLabel(scope);
      }
      return this.getRowIdentity(scope.row, scope.column);
    },
    getSelectLabel({ row, column }) {
      let renderOpts = this.renderOpts;
      let props = renderOpts.props || this.renderOpts.attrs || {};
      let labelProp = renderOpts.optionProps.label;
      let valueProp = renderOpts.optionProps.value;
      let optionsProp = renderOpts.optionGroupProps.options;
      let value = this.getRowIdentity(row, column);
      if (!(value === null || value === undefined || value === "")) {
        return (props.multiple ? value : [value])
          .map(
            renderOpts.optionGroups
              ? value => {
                  let selectItem = XEUtils.find(
                    renderOpts.optionGroups,
                    group =>
                      group[optionsProp].find(item => item[valueProp] === value)
                  );
                  return selectItem ? selectItem[labelProp] : null;
                }
              : value => {
                  let selectItem = XEUtils.find(
                    renderOpts.options,
                    item => item[valueProp] === value
                  );
                  return selectItem ? selectItem[labelProp] : null;
                }
          )
          .join(";");
      }
      return null;
    },
    getCascaderLabel({ row, column }) {
      let values = this.getRowIdentity(row, column) || [];
      let labels = [];
      let props = this.renderOpts.props || this.renderOpts.attrs || {};
      let matchCascaderData = function(index, list) {
        let val = values[index];
        if (list && values.length > index) {
          list.forEach(item => {
            if (item.value === val) {
              labels.push(item.label);
              matchCascaderData(++index, item.children);
            }
          });
        }
      };
      matchCascaderData(0, props.options || []);
      return (props.showAllLevels === false
        ? labels.slice(labels.length - 1, labels.length)
        : labels
      ).join(` ${props.separator || "/"} `);
    },
    getTimePickerLabel({ row, column }) {
      let value = this.getRowIdentity(row, column);
      let props = this.renderOpts.props || this.renderOpts.attrs || {};
      if (props.isRange) {
        return value
          ? value
              .map(date =>
                XEUtils.toDateString(date, props.format || "hh:mm:ss")
              )
              .join(` ${props.rangeSeparator || "-"} `)
          : "";
      }
      return XEUtils.toDateString(value, props.format || "hh:mm:ss");
    },
    getDatePickerLabel({ row, column }) {
      let value = this.getRowIdentity(row, column);
      let props = this.renderOpts.props || this.renderOpts.attrs || {};
      switch (props.type) {
        case "week":
          return this.getFormatDate(value, props, "yyyywWW");
        case "month":
          return this.getFormatDate(value, props, "yyyy-MM");
        case "year":
          return this.getFormatDate(value, props, "yyyy");
        case "dates":
          return this.getFormatDates(value, props, ", ", "yyyy-MM-dd");
        case "daterange":
          return this.getFormatDates(
            value,
            props,
            ` ${props.rangeSeparator || "-"} `,
            "yyyy-MM-dd"
          );
        case "datetimerange":
          return this.getFormatDates(
            value,
            props,
            ` ${props.rangeSeparator || "-"} `,
            "yyyy-MM-dd HH:ss:mm"
          );
      }
      return this.getFormatDate(value, props, "yyyy-MM-dd");
    },
    getFormatDate(value, props, defaultFormat) {
      return XEUtils.toDateString(value, props.format || defaultFormat);
    },
    getFormatDates(values, props, separator, defaultFormat) {
      return XEUtils.toArray(values)
        .map(date => this.getFormatDate(date, props, defaultFormat))
        .join(separator);
    },
    checkRequired({ column, store }) {
      let property = column.property;
      let editRules = this.$editable.editRules;
      if (property && editRules) {
        let columnRules = XEUtils.get(editRules, property);
        if (columnRules) {
          return columnRules.some(rule => rule.required === true);
        }
      }
      return false;
    },
    isEditRender({ row, column }) {
      return (
        this.renderOpts.type === "visible" ||
        (row.editActive &&
          (this.editConfig.mode === "row"
            ? row.editActive
            : row.editActive === column.property))
      );
    },
    checkIcon({ column, store }) {
      return column.property && this.editConfig.showIcon;
    },
    sortByEvent(row, index) {
      return this.sortBy(row.data, index);
    },
    sortMethodEvent(v1, v2) {
      return this.sortMethod(v1.data, v2.data);
    },
    selectableEvent(row, index) {
      if (this.selectable) {
        return this.selectable(row.data, index);
      }
      return !row.data._disabled;
    },
    filterMethodEvent(value, row, column) {
      return this.filterMethod(value, row.data, column);
    },
    checkAllEvent() {
      let status = this.checkAll;
      let _fullData = this.$editable._fullData;
      _fullData.forEach(item => {
        item.scrollChecked = status;
      });
      this.isIndeterminate = false;
      this.$editable.$emit(
        "select-all",
        status ? _fullData.map(item => (item ? item.data : item)) : []
      );
      this.$editable._updateSelection(status ? _fullData : []);
    },
    checkRowEvent({ row }) {
      let selection = this.handleCheckRow();
      this.$editable.$emit(
        "select",
        selection.map(item => (item ? item.data : item)),
        row.data
      );
      this.$editable._updateSelection(selection);
    },
    handleCheckRow() {
      if (this.type === "selection" && this.scrollLoad) {
        let _fullData = this.$editable._fullData;
        let selection = _fullData.filter(item => item.scrollChecked);
        let isIndeterminate = selection.length > 0;
        this.isIndeterminate = isIndeterminate;
        if (isIndeterminate && _fullData.every(item => item.scrollChecked)) {
          this.checkAll = true;
          this.isIndeterminate = false;
        } else {
          this.checkAll = false;
        }
        return selection;
      }
    }
  },
  mounted(){
  }
};
</script>

<style src="../../editable-column.css"></style>
<style lang="less">
  .el-tooltip__popper {
    max-width: 400px;
  }
</style>
